const h = (tag, props, children) => {
    //vnode就是一个JavaScript对象 h函数返回一个vnode
    return {
        tag,
        props,
        children
    }
}

const mount = (vnode, container) => {
    //vnode -element节点 
    //1.根据vnode创建一个元素 并且在vnode节点上也会保存一份真实dom
    const el = vnode.el = document.createElement(vnode.tag)
    //2.处理props 
    if (vnode.props) { //判断是否穿过来的有值 有可能传入null
        for (const key in vnode.props) {
            const value = vnode.props[key];

            if (key.startsWith("on")) { // 对事件监听的判断
                el.addEventListener(key.slice(2).toLowerCase(), value) //截掉头两位字母 on 并将驼峰式命名改成小写
            } else {
                el.setAttribute(key, value);  //设置属性
            }
        }
    }
    // 3.处理children
    if (vnode.children) {
        //判断类型 字符串直接设置
        if (typeof vnode.children === "string") {
            el.textContent = vnode.children;
        } else {
            //如果不是字符串的话 就是一个数组 数组里面不就是vnode节点么  在走一遍mount就完事了
            vnode.children.forEach(item => {
                //递归调用 将里面的内容挂载到el上面实现的tag节点
                mount(item, el);
            })
        }
    }
    // 4.将el挂载到container上  添加子节点 appendChild
    container.appendChild(el);
}

const patch = (n1, n2) => {
    //第一步判断类型是否一样  类型不同移除旧的节点 挂载新的节点 eg：div和h2节点不一样 直接就选择移除 重新挂载
    if (n1.tag !== n2.tag) {
        //拿到当前元素的父元素 parentElement  才能移除掉当前的元素
        const n1ElParent = n1.el.parentElement;
        n1ElParent.removeChild(n1.el);  //移除子元素
        mount(n2, n1ElParent);  //将新节点重新挂载
    } else {
        // 1.取出element对象, 并且在n2中进行保存 因为el上面的值 要改变 比如class改变了
        const el = n2.el = n1.el;
        // 2.处理props  判断props是否有值 没有值赋值一个空的对象
        const oldProps = n1.props || {};
        const newProps = n2.props || {};
        // 2.1.获取所有的newProps添加到el
        for (const key in newProps) {
            const oldValue = oldProps[key];
            const newValue = newProps[key];
            //判断两个值是不是相同的 如果是相同的 就不需要重新设置属性
            if (newValue !== oldValue) {
                if (key.startsWith("on")) { // 对事件监听的判断
                    el.addEventListener(key.slice(2).toLowerCase(), newValue)
                } else {
                    el.setAttribute(key, newValue);
                }
            }
        }

        // 2.2.删除旧的props
        for (const key in oldProps) {
            if (key.startsWith("on")) { // 对事件监听的判断 key 以on开头移除掉旧的属性
                const oldvalue = oldProps[key];
                //移除事件需要value
                el.removeEventListener(key.slice(2).toLowerCase(), oldvalue)
            }
            if (!(key in newProps)) {
                el.removeAttribute(key);
            }
        }

        // 3.处理children  没有值的话给一个空的数组
        const oldChildren = n1.children || [];
        const newChidlren = n2.children || [];

        if (typeof newChidlren === "string") { // 情况一: newChildren本身是一个string 直接给之前的children来替换
            // 边界情况 (edge case)
            if (typeof oldChildren === "string") {
                if (newChidlren !== oldChildren) {
                    el.textContent = newChidlren
                }
            } else {
                el.innerHTML = newChidlren;
            }
        } else { // 情况二: newChildren本身是一个数组

            if (typeof oldChildren === "string") {
                el.innerHTML = "";    //如果是一个string 给原来oldchildren清空
                newChidlren.forEach(item => {  //新数组里面的东西 进行挂载
                    mount(item, el);
                })
                //oldChildren 和newChildren两者都是数组的情况下
            } else {
                // oldChildren: [v1, v2, v3, v8, v9]
                // newChildren: [v1, v5, v6] 
                /* 
                需要移动节点来进行patch对比
                */
                /* 
                * 拿到相对更短的长度遍历 如果是现在的vnode节点短的话 原来的vnode节点长 就进行删除操作 
                *  如果是现在的vnode节点长 就进行添加挂载操作
                */

                // 1.前面有相同节点的原生进行patch操作
                const commonLength = Math.min(oldChildren.length, newChidlren.length);
                for (let i = 0; i < commonLength; i++) {
                    //调用patch 进行递归调用 自己去比对一些子节点 里面的是数组还是字符串
                    patch(oldChildren[i], newChidlren[i]);
                }

                // 2.newChildren.length > oldChildren.length
                if (newChidlren.length > oldChildren.length) {
                    //对老的数组做一个切割遍历 然后挂载多出来的那两个节点
                    newChidlren.slice(oldChildren.length).forEach(item => {
                        mount(item, el);
                    })
                }

                // 3.newChildren.length < oldChildren.length
                if (newChidlren.length < oldChildren.length) {
                    //截出来新的vnode节点长度 进行卸载操作
                    oldChildren.slice(newChidlren.length).forEach(item => {
                        el.removeChild(item.el);
                    })
                }
            }
        }
    }
}
